package torres.map;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;

import torres.controller.Direction;
import torres.fileIO.ClassContainer;
import torres.fileIO.BasicContainer;
import torres.fileIO.Saveable;

/**
 * @author Torres
 * Represents a two-dimensional Cartesian coordinate
 */
public class Location implements Saveable{
	
	private int x; int y;
	
	/**
	 * Creates a new Location of (-1, -1)
	 */
	public Location() {
		
		this.x = -1;
		this.y = -1;
	}
	
	/**
	 * Creates a new Location of (x,y)
	 * @param x The x-coordinate of the location.
	 * @param y The y-coordinate of the location.
	 */
	public Location(int x, int y) {
		
		this.x = x;
		this.y = y;
	}
	
	/**
	 * Returns the current x-coordinate of this Location.
	 * @return The current x-coordinate of this Location.
	 */
	public int getX() {	
		return x;
	}
	
	/**
	 * Returns the current y-coordinate of this Location.
	 * @return The current y-coordinate of this Location.
	 */
	public int getY() {
		return y;
	}
	
	/**
	 * Sets the x-coordinate of this Location to the specified value.
	 * @param x The new x-coordinate of this Location.
	 */
	public void setX(int x) {	
		this.x = x;
	}
	
	/**
	 * Sets the y-coordinate of this Location to the specified value.
	 * @param y The new y-coordinate of this Location.
	 */
	public void setY(int y) {
		this.y = y;
	}
	
	/**
	 * Returns the String representation of this Location.
	 * @return The String representation of this Location.
	 */
	public String toString() {	
		return ("Location: row=" + y + " column=" + x);
	}
	
	/* (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	public boolean equals(Object o) {
		Location l = (Location)o;
		return l.getX() == this.getX() && l.getY() == this.getY();
	}
	
	/* (non-Javadoc)
	 * @see java.lang.Object#hashCode()
	 */
	public int hashCode() {
		return this.toString().hashCode();
	}
	
	/**
	 * @param base
	 * @param dir
	 * @return
	 */
	public static Location locationInDirection(Location base, Direction dir) {
		return new Location(base.getX() + dir.deltaX(), base.getY() + dir.deltaY());
	}
	
	/**
	 * @param a
	 * @param b
	 * @return
	 */
	public static int roundedDistance(Location a, Location b) {
		return (int)(Math.sqrt( (a.getX() - b.getX())*(a.getX() - b.getX()) + (a.getY() - b.getY())*(a.getY() - b.getY())) + 0.5);
	}
	/**
	 * @param a
	 * @param b
	 * @return
	 */
	public static int flooredDistance(Location a, Location b) {
		return (int)(Math.sqrt( (a.getX() - b.getX())*(a.getX() - b.getX()) + (a.getY() - b.getY())*(a.getY() - b.getY())));
		}
	
	/**
	 * @param base
	 * @param dir
	 * @param radius
	 * @return
	 */
	public static Collection<Collection<Location>> linearCollection(Location base, Direction dir, int radius) {
		Collection<Collection<Location>> ret = new ArrayList<Collection<Location>>();
		Location loc = base;
		for(int a = 0; a <= radius; ++a) {
			Collection<Location> curCollection = new ArrayList<Location>();
			curCollection.add(loc);
			ret.add(curCollection);
			loc = Location.locationInDirection(loc, dir);
		}
		return ret;
	}
	
	/**
	 * @param base
	 * @param dir
	 * @param radius
	 * @return
	 */
	public static Collection<Collection<Location>> angularCollection(Location base, Direction dir, int radius) {
		Collection<Collection<Location>> ret = new ArrayList<Collection<Location>>();
		Collection<Location> reject = new ArrayList<Location>(100);
		
		Collection<Location> next = new ArrayList<Location>();
		next.add(base);
		
		for(int a = 0; a <= radius; ++a) {
			ret.add(next);
			next = getNextAngularLocation(next, a, reject, base, dir);
		}
		return ret;
	}
	
	/**
	 * @param base
	 * @param radius
	 * @return
	 */
	public static Collection<Collection<Location>> radialCollection(Location base, int radius) {
		Collection<Collection<Location>> ret = new ArrayList<Collection<Location>>();
		
		Collection<Location> next = new HashSet<Location>();
		next.add(base);
		
		for(int a = 0; a <= radius; ++a) {
			ret.add(next);
			next = getLocationsOfRadius(next, a, base);
		}
		return ret;
	}
	
	/**
	 * @param prev
	 * @param radius
	 * @param reject
	 * @param base
	 * @param dir
	 * @return
	 */
	private static Collection<Location> getNextAngularLocation(Collection<Location> prev, int radius, Collection<Location> reject, Location base, Direction dir) {
		Collection<Location> next = new HashSet<Location>();
		Location nn = null;

		for(Location l : prev) {
			Direction[] directions = Direction.values();
			if(directions[0] == dir) {
				nn = Location.locationInDirection(l, directions[directions.length-1]);
				if(Location.flooredDistance(base, nn) == radius+1) {
					next.add(nn);
				}
				nn = Location.locationInDirection(l, directions[0]);
				if(Location.flooredDistance(base, nn) == radius+1) {
					next.add(nn);
				}
				nn = Location.locationInDirection(l, directions[1]);
				if(Location.flooredDistance(base, nn) == radius+1) {
					next.add(nn);
				}
			}
			for(int i = 1; i < directions.length; ++i){
				if(directions[i] == dir) {
					for(int j = -1; j < 2; ++j) {
						nn = Location.locationInDirection(l, directions[(i+j)%directions.length]);
						if(Location.flooredDistance(base, nn) == radius+1)
							next.add(nn);
						else 
							reject.add(nn);						
					}
				}
			}
		}
		Iterator<Location> iter = reject.iterator();
		while (iter.hasNext()) {
			Location nextElement = iter.next();
			if(Location.flooredDistance(base, nextElement) == radius+1) {
				next.add(nextElement);
				iter.remove();
			}
		}
		
		return next;
	}
	
	/**
	 * @param prev
	 * @param radius
	 * @param base
	 * @return
	 */
	private static Collection<Location> getLocationsOfRadius(Collection<Location> prev, int radius, Location base) {
		Collection<Location> next = new HashSet<Location>();
		for(Location l : prev) {
			for(Direction d : Direction.values()){
				Location nn = Location.locationInDirection(l, d);
				if(Location.roundedDistance(base, nn) == (radius+1)) 
					next.add(nn);
			}			
		}		
		return next;
	}
	
	/* (non-Javadoc)
	 * @see torres.fileIO.Saveable#save()
	 */
	@Override
	public ClassContainer save() {
		ClassContainer curClass = new ClassContainer("Location");
		curClass.addProperty(new BasicContainer("x", x));
		curClass.addProperty(new BasicContainer("y", y));
		
		return curClass;
	}

	/* (non-Javadoc)
	 * @see torres.fileIO.Saveable#load(torres.fileIO.ClassContainer)
	 */
	@Override
	public void load(ClassContainer classContainer) {
		BasicContainer xCont = (BasicContainer)classContainer.getProperty("x");
		x = Integer.parseInt((String)xCont.getValue());
		BasicContainer yCont = (BasicContainer)classContainer.getProperty("y");
		y = Integer.parseInt((String)yCont.getValue());
		
	}
 }
